package io.kiki.stack.netty.invoker;

import io.kiki.stack.netty.command.Command;
import io.kiki.stack.netty.command.CommandFactory;
import io.kiki.stack.netty.command.CommandHandler;
import io.kiki.stack.netty.command.ResponseCommand;
import io.kiki.stack.netty.protocol.Protocol;
import io.kiki.stack.netty.protocol.ProtocolImpl;
import io.netty.util.Timeout;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;


@Slf4j
@Setter
@Getter
public class FutureImpl implements Future {


    private final CountDownLatch countDownLatch = new CountDownLatch(1);
    private final AtomicBoolean executeCallbackOnlyOnce = new AtomicBoolean(false);
    private final int invokeId;
    private final CallbackListener callbackListener;
    private final Callback callback;
    private final ClassLoader classLoader;

    private final CommandFactory commandFactory;
    private volatile ResponseCommand responseCommand;
    private Timeout timeout;
    private Throwable cause;
    private RequestContext requestContext;

    public FutureImpl(int invokeId, CallbackListener callbackListener, Callback callback, CommandFactory commandFactory) {
        this.invokeId = invokeId;
        this.callbackListener = callbackListener;
        this.callback = callback;
        this.classLoader = Thread.currentThread().getContextClassLoader();

        this.commandFactory = commandFactory;
    }

    public FutureImpl(int invokeId, CallbackListener callbackListener, Callback callback, CommandFactory commandFactory, RequestContext requestContext) {
        this(invokeId, callbackListener, callback, commandFactory);
        this.requestContext = requestContext;
    }

    @Override
    public ResponseCommand waitResponse(long timeoutMillis) throws InterruptedException {
        this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
        return this.responseCommand;
    }

    @Override
    public ResponseCommand waitResponse() throws InterruptedException {
        this.countDownLatch.await();
        return this.responseCommand;
    }

    @Override
    public Command createChannelClosedResponse(InetSocketAddress responseHost) {
        return this.commandFactory.createChannelClosedResponse(responseHost, null);
    }

    @Override
    public void putResponse(Command response) {
        this.responseCommand = (ResponseCommand) response;
        this.countDownLatch.countDown();
    }

    @Override
    public boolean isDone() {
        return this.countDownLatch.getCount() <= 0;
    }

    @Override
    public ClassLoader getAppClassLoader() {
        return this.classLoader;
    }

    @Override
    public int id() {
        return this.invokeId;
    }

    @Override
    public void executeInvokeCallback() {
        if (callbackListener != null) {
            if (this.executeCallbackOnlyOnce.compareAndSet(false, true)) {
                callbackListener.onResponse(this);
            }
        }
    }

    @Override
    public Callback getInvokeCallback() {
        return this.callback;
    }

    @Override
    public void addTimeout(Timeout timeout) {
        this.timeout = timeout;
    }

    @Override
    public void cancelTimeout() {
        if (this.timeout != null) {
            this.timeout.cancel();
        }
    }

    @Override
    public Throwable getCause() {
        return this.cause;
    }

    @Override
    public void setCause(Throwable cause) {
        this.cause = cause;
    }


    @Override
    public RequestContext getInvokeContext() {
        return requestContext;
    }

    @Override
    public void setInvokeContext(RequestContext requestContext) {
        this.requestContext = requestContext;
    }

    @Override
    public void tryAsyncExecuteInvokeCallbackAbnormally() {
        try {
            Protocol protocol = ProtocolImpl.protocol;
            CommandHandler commandHandler = protocol.getCommandHandler();
            if (null != commandHandler) {
                Executor executorService = commandHandler.getDefaultExecutor();
                if (null != executorService) {
                    executorService.execute(new Runnable() {
                        @Override
                        public void run() {
                            ClassLoader oldClassLoader = null;
                            try {
                                if (FutureImpl.this.getAppClassLoader() != null) {
                                    oldClassLoader = Thread.currentThread().getContextClassLoader();
                                    Thread.currentThread().setContextClassLoader(FutureImpl.this.getAppClassLoader());
                                }
                                FutureImpl.this.executeInvokeCallback();
                            } finally {
                                if (null != oldClassLoader) {
                                    Thread.currentThread().setContextClassLoader(oldClassLoader);
                                }
                            }
                        }
                    });
                }
            } else {
                log.error("Executor null in commandHandler of protocolCode [{}].");
            }
        } catch (Exception e) {
            log.error("Exception caught when executing invoke callback abnormally.", e);
        }
    }

}
